/**
 * File: /workspaces/TVSpider/lib/quark_api.js
 * Project: /workspaces/TVSpider
 * Created Date: Monday, May 20th 2024, 6:38:12 am
 * Author: jade
 * -----
 * Last Modified: Tue May 21 2024
 * Modified By: jade
 * -----
 * Copyright (c) 2024 samples
 * ------------------------------------
 * Javascript will save your soul!
 */
import { _, Crypto } from '../encodings/cat';
import * as Utils from '../index';
import { Item } from './quark_object';

class Quark {
  constructor() {
    this.apiUrl = 'https://drive-pc.quark.cn/1/clouddrive/';
    this.cookie = '';
    this.ckey = '';
    this.shareTokenCache = {};
    this.pr = 'pr=ucpro&fr=pc';
    this.subtitleExts = ['.srt', '.ass', '.scc', '.stl', '.ttml'];
    this.saveFileIdCaches = {};
    this.saveDirId = null;
    this.saveDirName = 'TV';
    this.isVip = false;
  }
  async initQuark(cookie) {
    this.ckey = Crypto.enc.Hex.stringify(Crypto.MD5(cookie)).toString();
    this.cookie = cookie;
    this.isVip = await this.getVip();
  }

  getHeaders() {
    return {
      'User-Agent':
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch',
      Referer: 'https://pan.quark.cn/',
      'Content-Type': 'application/json',
      Cookie: this.cookie,
      Host: 'drive-pc.quark.cn',
    };
  }

  async api(url, data, retry, method) {
    const leftRetry = retry || 3;
    let resp = await req(this.apiUrl + url, {
      method: method || 'post',
      data: data,
      headers: this.getHeaders(),
    });
    if (resp.headers['set-cookie']) {
      const puus = [resp.headers['set-cookie']].join(';;;').match(/__puus=([^;]+)/);
      if (puus) {
        if (this.cookie.match(/__puus=([^;]+)/)[1] != puus[1]) {
          this.cookie = this.cookie.replace(/__puus=[^;]+/, `__puus=${puus[1]}`);
        }
      }
    }
    if (resp.code !== 200 && leftRetry > 0) {
      Utils.sleep(1);
      return await this.api(url, data, leftRetry - 1);
    }
    return JSON.parse(resp.content) || {};
  }

  getShareData(url) {
    let regex = /https:\/\/pan\.quark\.cn\/s\/([^\\|#/]+)/;
    let matches = regex.exec(url);
    if (matches) {
      return {
        shareId: matches[1],
        folderId: '0',
      };
    }
    return null;
  }

  async getVip() {
    const listData = await this.api(
      `member?pr=ucpro&fr=pc&uc_param_str=&fetch_subscribe=true&_ch=home&fetch_identity=true`,
      null,
      null,
      'get',
    );
    if (listData.data.member_type === 'EXP_SVIP') {
      return true;
    } else {
      return false;
    }
  }

  getPlayFormatList() {
    if (this.isVip) {
      return ['4K', '超清', '高清', '普画'];
    } else {
      return ['普画'];
    }
  }

  getPlayFormtQuarkList() {
    if (this.isVip) {
      return ['4k', '2k', 'super', 'high', 'normal', 'low'];
    }
    {
      return ['low'];
    }
  }

  async getShareToken(shareData) {
    if (!this.shareTokenCache[shareData.shareId]) {
      delete this.shareTokenCache[shareData.shareId];
      const shareToken = await this.api(`share/sharepage/token?${this.pr}`, {
        pwd_id: shareData.shareId,
        passcode: shareData.sharePwd || '',
      });
      if (shareToken.data && shareToken.data.stoken) {
        this.shareTokenCache[shareData.shareId] = shareToken.data;
      }
    }
  }
  async listFile(shareIndex, shareData, videos, subtitles, shareId, folderId, page) {
    const prePage = 200;
    page = page || 1;
    const listData = await this.api(
      `share/sharepage/detail?${this.pr}&pwd_id=${shareId}&stoken=${encodeURIComponent(this.shareTokenCache[shareId].stoken)}&pdir_fid=${folderId}&force=0&_page=${page}&_size=${prePage}&_sort=file_type:asc,file_name:asc`,
      null,
      null,
      'get',
    );
    if (!listData.data) return [];
    const items = listData.data.list;
    if (!items) return [];
    const subDir = [];
    for (const item of items) {
      if (item.dir === true) {
        subDir.push(item);
      } else if (item.file === true && item.obj_category === 'video') {
        if (item.size < 1024 * 1024 * 5) continue;
        item.stoken = this.shareTokenCache[shareData.shareId].stoken;
        videos.push(Item.objectFrom(item, shareData.shareId, shareIndex));
      } else if (item.type === 'file' && this.subtitleExts.some((x) => item.file_name.endsWith(x))) {
        subtitles.push(Item.objectFrom(item, shareData, shareIndex));
      }
    }
    if (page < Math.ceil(listData.metadata._total / prePage)) {
      const nextItems = await this.listFile(
        shareIndex,
        shareData.shareId,
        videos,
        subtitles,
        shareId,
        folderId,
        page + 1,
      );
      for (const item of nextItems) {
        items.push(item);
      }
    }
    for (const dir of subDir) {
      const subItems = await this.listFile(shareIndex, shareData, videos, subtitles, shareId, dir.fid);
      for (const item of subItems) {
        items.push(item);
      }
    }
    return items;
  }

  findBestLCS(mainItem, targetItems) {
    const results = [];
    let bestMatchIndex = 0;
    for (let i = 0; i < targetItems.length; i++) {
      const currentLCS = Utils.lcs(mainItem.name, targetItems[i].name);
      results.push({ target: targetItems[i], lcs: currentLCS });
      if (currentLCS.length > results[bestMatchIndex].lcs.length) {
        bestMatchIndex = i;
      }
    }
    const bestMatch = results[bestMatchIndex];
    return { allLCS: results, bestMatch: bestMatch, bestMatchIndex: bestMatchIndex };
  }

  async getFilesByShareUrl(shareIndex, shareInfo, videos, subtitles) {
    const shareData = typeof shareInfo === 'string' ? this.getShareData(shareInfo) : shareInfo;
    if (!shareData) return [];
    await this.getShareToken(shareData);
    if (!this.shareTokenCache[shareData.shareId]) return [];
    await this.listFile(shareIndex, shareData, videos, subtitles, shareData.shareId, shareData.folderId);
    if (subtitles.length > 0) {
      videos.forEach((item) => {
        var matchSubtitle = this.findBestLCS(item, subtitles);
        if (matchSubtitle.bestMatch) {
          item.subtitle = matchSubtitle.bestMatch.target;
        }
      });
    }
  }

  clean() {
    const saves = Object.keys(this.saveFileIdCaches);
    for (const save of saves) {
      delete this.saveFileIdCaches[save];
    }
  }

  async clearSaveDir() {
    const listData = await this.api(
      `file/sort?${this.pr}&pdir_fid=${this.saveDirId}&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`,
      {},
      {},
      'get',
    );
    if (listData.data && listData.data.list && listData.data.list.length > 0) {
      await this.api(`file/delete?${this.pr}`, {
        action_type: 2,
        filelist: listData.data.list.map((v) => v.fid),
        exclude_fids: [],
      });
    }
  }

  async createSaveDir(clean) {
    if (this.saveDirId) {
      // 删除所有子文件
      if (clean) await this.clearSaveDir();
      return;
    }
    const listData = await this.api(
      `file/sort?${this.pr}&pdir_fid=0&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`,
      {},
      {},
      'get',
    );
    if (listData.data && listData.data.list)
      for (const item of listData.data.list) {
        if (item.file_name === this.saveDirName) {
          this.saveDirId = item.fid;
          await this.clearSaveDir();
          break;
        }
      }
    if (!this.saveDirId) {
      const create = await this.api(`file?${this.pr}`, {
        pdir_fid: '0',
        file_name: this.saveDirName,
        dir_path: '',
        dir_init_lock: false,
      });
      if (create.data && create.data.fid) {
        this.saveDirId = create.data.fid;
      }
    }
  }

  async save(shareId, stoken, fileId, fileToken, clean) {
    await this.createSaveDir(clean);
    if (clean) {
      this.clean();
    }
    if (!this.saveDirId) return null;
    if (!stoken) {
      await this.getShareToken({
        shareId: shareId,
      });
      if (!this.shareTokenCache[shareId]) return null;
    }
    const saveResult = await this.api(`share/sharepage/save?${this.pr}`, {
      fid_list: [fileId],
      fid_token_list: [fileToken],
      to_pdir_fid: this.saveDirId,
      pwd_id: shareId,
      stoken: stoken || this.shareTokenCache[shareId].stoken,
      pdir_fid: '0',
      scene: 'link',
    });
    if (saveResult.data && saveResult.data.task_id) {
      let retry = 0;
      // wait task finish
      while (true) {
        const taskResult = await this.api(
          `task?${this.pr}&task_id=${saveResult.data.task_id}&retry_index=${retry}`,
          {},
          {},
          'get',
        );
        if (
          taskResult.data &&
          taskResult.data.save_as &&
          taskResult.data.save_as.save_as_top_fids &&
          taskResult.data.save_as.save_as_top_fids.length > 0
        ) {
          return taskResult.data.save_as.save_as_top_fids[0];
        }
        retry++;
        if (retry > 2) break;
        Utils.sleep(1);
      }
    }
    return false;
  }

  async getLiveTranscoding(shareId, stoken, fileId, fileToken, flag) {
    if (!this.saveFileIdCaches[fileId]) {
      const saveFileId = await this.save(shareId, stoken, fileId, fileToken, true);
      if (!saveFileId) return null;
      this.saveFileIdCaches[fileId] = saveFileId;
    }
    const transcoding = await this.api(`file/v2/play?${this.pr}`, {
      fid: this.saveFileIdCaches[fileId],
      resolutions: 'normal,low,high,super,2k,4k',
      supports: 'fmp4',
    });

    if (transcoding.data && transcoding.data.video_list) {
      let flag_id = flag.split('-').slice(-1)[0];
      let index = Utils.findAllIndexes(this.getPlayFormatList(), flag_id);
      let quarkFormat = this.getPlayFormtQuarkList()[index];
      for (const video of transcoding.data.video_list) {
        if (video.resolution === quarkFormat) {
          return video.video_info.url;
        }
      }
      return transcoding.data.video_list[index].video_info.url;
    }
    return null;
  }

  async getDownload(shareId, stoken, fileId, fileToken, clean) {
    if (!this.saveFileIdCaches[fileId]) {
      const saveFileId = await this.save(shareId, stoken, fileId, fileToken, clean);
      if (!saveFileId) return null;
      this.saveFileIdCaches[fileId] = saveFileId;
    }
    const down = await this.api(`file/download?${this.pr}&uc_param_str=`, {
      fids: [this.saveFileIdCaches[fileId]],
    });
    if (down.data) {
      return down.data[0];
    }
    return null;
  }
}
export { Quark };
